R-Version: [Default] [64-bit] C:\Program Files\R\R-4.1.0

Imports

In folgendem Junk werden alle Tabellen aus den CSV’s eingelesen. Doku Daten: https://sorry.vse.cz/~berka/challenge/PAST/index.html (rechte Seite PKDD’99 Challenge > Data > Financial Data Description)

Einlesen der Daten

Der Datensatz besteht aus acht verschiedenen Tabellen, welche teils durch Keys miteinander verknüpft sind.

root_path <- "./xselling_banking_data-1/xselling_banking_data/"

accounts <- read.csv(paste0(root_path, "account.csv"), header = TRUE, sep = ";")
cards <- read.csv(paste0(root_path, "card.csv"), header = TRUE, sep = ";")
clients <- read.csv(paste0(root_path, "client.csv"), header = TRUE, sep = ";")
dispositions <- read.csv(paste0(root_path, "disp.csv"), header = TRUE, sep = ";")
districts <- read.csv(paste0(root_path, "district.csv"), sep = ";")
loans <- read.csv(paste0(root_path, "loan.csv"), header = TRUE, sep = ";")
orders <- read.csv(paste0(root_path, "order.csv"), header = TRUE, sep = ";")
transactions <- read.csv(paste0(root_path, "trans.csv"), header = TRUE, sep = ";")

Cleaning

Accounts

sample_n(accounts, 5)

Die Account-Tabelle enthält vier Kolonnen: die Account-ID, die District-ID (welche auf die District-Tabelle verweist), die Frequenz, welche die Häufigkeit der Ausstellung der Abrechnungen als Kategorie besagt, und das Erstellungsdatum des Accounts. Die Frequenz kann eine von drei verschiedenen Werten annehmen.

unique(accounts$frequency)
[1] "POPLATEK MESICNE"   "POPLATEK PO OBRATU" "POPLATEK TYDNE"    

Nachfolgend sollen die Frequenz-Werte übersetzt und das Datum in ein richtiges Format transformiert werden. Ausserdem soll die Tabelle auf fehlende Werte überprüft werden.

accounts$date <- as.Date(as.character(accounts$date), format= "%y%m%d")

accounts$frequency[accounts$frequency == "POPLATEK MESICNE"]   <- "monthly"
accounts$frequency[accounts$frequency == "POPLATEK TYDNE"]     <- "weekly"
accounts$frequency[accounts$frequency == "POPLATEK PO OBRATU"] <- "after_transaction"

sum(is.na(accounts))
[1] 0

Es gibt also keine fehlende Werte in diesem Dataframe.

Cards

sample_n(cards, 5)

Auch bei card muss das Datum umgewandelt werden, der zeitliche Teil wird ignoriert, da er immer 0 ist.

cards$issued <- as.Date(as.character(cards$issued), format= "%y%m%d")

cards$type[cards$type == "gold"]     <- "classic"
cards <- filter(cards, type == "classic")

sum(is.na(cards))
[1] 0

Clients

sample_n(clients, 10)

In der Tabelle existiert die Spalte birth_number, welcher man auf den ersten Blick die Datumsräpresentation nicht ansieht. In der Doku wird die Struktur deutlich, sie ist für Männer YYMMDD und für Frauen YYMMDD+50DD. In Folge wird die Nummer in ihre Datumsräpresentation konvertiert und die Spalte “gender” als male/female aufgeschlüsselt. Zudem wird das Alter der clients bezogen auf das Jahr 1999 herausextrahiert, da der Datensatz aus diesem Jahr stammt. Es wird nicht year(Sys.Date()) verwendet, damit die Daten auch in Zukunft konsistent blieben.

# Months above 12 must be female
clients <- mutate(clients, gender = 
                     ifelse(substr(birth_number, 3, 4) > 12, "female", "male"))

# Substract the 50 to get the birth month
clients <- mutate(clients, birth_month =
                     ifelse(as.numeric(substr(birth_number, 3, 4)) > 12,
                            as.numeric(substr(birth_number, 3, 4)) - 50,
                            as.numeric(substr(birth_number, 3, 4))))

# Transform the birth_number to a date
clients <- mutate(clients, birth_number = paste("19",
                                                substr(birth_number, 1, 2), 
                                                str_pad(birth_month, 2,
                                                        pad = "0"),
                                                substr(birth_number, 5, 6),
                   sep = "", collapse = NULL))
clients$birth_date <- as.Date(as.character(clients$birth_number),
                               format= "%Y%m%d")

# Remove unused columns
clients$birth_month <- NULL
clients$birth_number <- NULL

# Get the age of the clients in the year 1999 and save it in a column
get_age <- function(birth_date) {
  base_year <- 99
  year <- substr(birth_date, 3, 4)
  result <- base_year - as.integer(year)
  
  return(result)
}
clients <- clients %>%
   mutate(age = get_age(birth_date))

sum(is.na(clients))
[1] 0
accounts
clients

Dispositions

sample_n(dispositions, 5)

Bei Dispositions sollen nur Owners verwendet werden, da die Analyse nur Eigentümer von Konten behandeln soll.

dispositions <- dispositions %>% filter(type == 'OWNER')

sum(is.na(dispositions))
[1] 0

Districts

Bei district sind die Spaltennamen der Tabelle abhanden gekommen. Hier werden die Tabellennamen umbenannt, gemäss Doku.

districts <- rename(districts, district_id = A1, district_name = A2, region = A3, 
                   inhabitants = A4, municipalities_inhabitants_smaller_499 = A5, 
                   municipalities_inhabitants_500_to_1999 = A6, 
                   municipalities_inhabitants_2000_to_9999 = A7, 
                   municipalities_inhabitants_larger_10000 = A8, cities = A9, 
                   urban_inhabitants_ratio = A10, average_salary = A11,
                   unemployment_rate_95 = A12, unemployment_rate_96 = A13,
                   entrepreneurs_per_1000 = A14, crimes_95 = A15,
                   crimes_96 = A16)

sum(is.na(districts))
[1] 0

Transactions

sample_n(transactions, 5)

In den Transaktionen muss das Datum gemäss Format YYMMDD konvertiert werden.

# Rename k_symbol
transactions <- rename(transactions, c("characterization" = "k_symbol")) 

# Change formats
transactions$date <- as.Date(as.character(transactions$date), format= "%y%m%d")
transactions$amount <- as.numeric(transactions$amount)
transactions$balance <- as.numeric(transactions$balance)

# Translate values
transactions$type[transactions$type == "PRIJEM"] <- "credit"
transactions$type[transactions$type == "VYDAJ"]  <- "withdrawal"
transactions$type[transactions$type == "VYBER"]  <- "withdrawal"

transactions$operation[transactions$operation == "VKLAD"]          <- "cash credit"
transactions$operation[transactions$operation == "PREVOD Z UCTU"]  <- "collection"
transactions$operation[transactions$operation == "VYBER"]          <- "cash withdrawal"
transactions$operation[transactions$operation == " "]              <- "unknown"
transactions$operation[transactions$operation == "PREVOD NA UCET"] <- "remittance"
transactions$operation[transactions$operation == "VYBER KARTOU"]   <- "card withdrawal"

transactions$characterization[transactions$characterization == " "] <- "unknown"
transactions$characterization[transactions$characterization == "DUCHOD"] <- "pension"
transactions$characterization[transactions$characterization == "UROK"] <- "interest"
transactions$characterization[transactions$characterization == "SIPO"] <- "household"
transactions$characterization[transactions$characterization == "SLUZBY"] <- "payment statement"
transactions$characterization[transactions$characterization == "POJISTNE"] <- "insurance"
transactions$characterization[transactions$characterization == "SANKC. UROK"]  <- "neg_interest"
transactions$characterization[transactions$characterization == "UVER"]  <- "loan_pay"

sum(is.na(transactions))
[1] 760931

Orders

sample_n(orders, 5)
# Rename column k_symbol
orders <- rename(orders, "characterization" = "k_symbol") 

# Translate column characterization
orders$characterization[orders$characterization == "SIPO"]     <- "household"
orders$characterization[orders$characterization == "UVER"]     <- "loan"
orders$characterization[orders$characterization == "POJISTNE"] <- "insurance"
orders$characterization[orders$characterization == "LEASING"]  <- "leasing"

# Categorize NA as unknown
orders$characterization[is.na(orders$characterization)] <- "unknown"

orders$amount <- as.numeric(orders$amount)

sum(is.na(loans))
[1] 0

Loans

sample_n(loans, 5)
loans$date <- as.Date(as.character(loans$date), format= "%y%m%d")
loans$payments <- as.numeric(loans$payments)
loans$amount <- as.numeric(loans$amount)

# Make column status human readable
loans$status[loans$status == "A"] <- "finished_payed"
loans$status[loans$status == "B"] <- "finished_not_payed"
loans$status[loans$status == "C"] <- "running_ok"
loans$status[loans$status == "D"] <- "running_in_debt"

sum(is.na(loans))
[1] 0

Zusammenfügen der Dataframes

In diesem Abschnitt werden die verschiedenen Tabellen zusammen gesetzt. Dabei werden loan, cards und district it left join angehängt, damit fehlende Spalten nicht den Datensatz verkleinern. Die Transaktionsdaten werden hier noch nicht zusammengeführt.

# Clients mit dispositions
full <- inner_join(clients, dispositions, by = "client_id", suffix = c(".client", ".dispositions"))
sum(duplicated(full$client_id))
[1] 0
# Full mit account
full <- inner_join(full, accounts, by = "account_id", suffix = c("", ".accounts"))
sum(duplicated(full$account_id))
[1] 0
# Full mit loan
sum(duplicated(loans$account_id))
[1] 0
full <- left_join(full, loans, by = "account_id", suffix = c("", ".loans"))

# Full mit cards
full <- left_join(full, cards, by = "disp_id", suffix = c("", ".cards"))
sum(duplicated(cards$disp_id))
[1] 0
# District Informations for client
full <- left_join(full, districts, by = "district_id")

# District informations for card
full <- left_join(full, districts, by = c("district_id.accounts"="district_id"), suffix = c("", ".accounts"))

sample_n(full, 5)

Jugendliche und Personen, welche während des Zeitraums des Datensatzes erst erwachsen worden sind, sollen nicht in die Auswertung einfliessen. Da sicher der Datensatz über einen Zeitraum von sechs Jahren erstreckt werden alle Clients jünger als 25 Jahre herausgefiltert.

full <- full %>% filter(age >= 25)
full

Als nächstes werden alle Zeilen mit Kreditkartenkäufern von den Nicht-Käufern getrennt

has_card_function <- function(x) {
  if (is.na(x)) {
    return(FALSE)
  } else {
    return(TRUE)
  }
}

# Erstelle die neue Spalte "has_card" mit der apply()-Funktion und der oben definierten Funktion
full$has_card <- sapply(full[, "card_id"], has_card_function)
full <- full %>% select(-card_id, -type.cards)

card_buyers <- full %>% filter(has_card == TRUE)

non_buyers <- full %>% filter(has_card == FALSE)

Jetzt können wir noch einige Variabeln entfernen, welche keinen Einfluss auf das Modell haben sollten.

Aufsummieren der Transaktionen

sample_n(transactions, 5)

Bei den Transaktionen ist jeweils die neue Balance und der Betrag der Transaktion angegebn. Das Problem dabei ist, dass alle Beträge positiv sind, auch wenn sie eigentlich abgezogen werden.

df <- transactions

# Konvertieren Sie das 'date'-Feld in ein Datum
df$date <- as.Date(df$date)

# Sortieren Sie das Dataframe nach Nutzer und Datum
df <- df[order(df$account_id, df$date), ]

# Gruppieren Sie das Dataframe nach Nutzer
df <- group_by(df, account_id)

# Iterieren Sie über jeden Nutzer und bearbeiten Sie die Transaktionen
df <- df %>% 
  summarize(transactions = {
    # Fügen Sie eine Spalte mit dem vorherigen Kontostand hinzu
    prev_balance <- ifelse(row_number() == 1, NA, lag(balance, order_by = date))

    # Berechnen Sie den Unterschied zwischen dem vorherigen Kontostand und dem aktuellen Kontostand
    difference <- balance - prev_balance

    # Fügen Sie eine Spalte mit der Transaktionsart hinzu
    type <- "add"
    type[difference < 0] <- "subtract"

    # Erstellen Sie das Dataframe mit den Transaktionen für jeden Nutzer
    transactions_df <- data.frame(amount, date, balance, prev_balance, difference, type)
    transactions_df
  }) %>%
  ungroup()

transactions <- unnest(df, transactions)

# Hinzufügen des ersten amounts bei jedem Account
transactions$difference <- ifelse(is.na(transactions$difference) & is.na(transactions$prev_balance) & (transactions$amount == transactions$balance), transactions$amount, transactions$difference)

transactions$amount <- NULL

transactions

Zusammenfassen der Transaktionen für Card Buyers

Um die Transaktions-Daten in unseren Modellen brauchen zu können, muss für jeden Kunde ein Rollup-Fenster erstellt werden. Dies fasst die Transaktionen der zwölf Monate vor dem Erhalt einer Kreditkarte zusammen (minus einen Monat Input Lag). Auf diesen Monaten werden die Transaktionen zusammengefasst.

Als erstes werden die Transaktionen von Kunden herausgefiltert, welche eine Kreditkarte haben.

account_ids <- card_buyers$account_id
buyer_transactions <- transactions[transactions$account_id %in% account_ids,]

Das issued-Datum soll zu den Transaktionen hinzugefügt werden, damit diese für jeden Kunden einzeln gefiltert werden können.

buyer_transactions <- merge(buyer_transactions, full[, c("account_id", "issued")], by="account_id")

Nun sollen Transaktionen so gefiltert werden, dass nur noch Transaktionen zwischen 13 Monaten und 1 Monat vor dem Issued Datum vorkommen.

filtered_df <- buyer_transactions %>%
  filter(date >= as.Date(paste0(format(issued - months(13), "%Y-%m"), "-01")) &
         date <= as.Date(paste0(format(issued - months(1), "%Y-%m"), "-01")) - 1)

Auf diesen Daten wird eine Gruppierung anhand der account_id und des Monats gemacht werden. Die Werte in difference und balance werden zu verschiedenen Metriken zusammengefasst: Auf beiden Werten erfassen wir das Minimum, das Maximum, den Durchschnitt, den Median und die Standardabweichung. Bei der balance erfassen wir die erste und die letzte Balance des Monats und bei difference die Anzahl positive und negative differences.

summary_df <- filtered_df %>%
  group_by(account_id, month = format(date, "%Y-%m")) %>%
  summarise(
    max_difference = max(difference),
    min_difference = min(difference),
    max_balance = max(balance),
    min_balance = min(balance),
    initial_balance = first(balance),
    end_balance = last(balance),
    mean_balance = mean(balance),
    median_balance = median(balance),
    std_balance = sd(balance),
    mean_difference = mean(difference),
    median_difference = median(difference),
    std_difference = sd(difference),
    count_positive_difference = sum(difference > 0),
    count_negative_difference = sum(difference < 0)
  )
summary_df <- summary_df %>%
  arrange(account_id)
summary_df

Jetzt haben wir für jede account_id eine Übersicht über die 12 Monate vor dem Kartenerhalt. Da es aber sein könnte, dass es Kunden gibt, welche nicht jeden Monat eine Transaktion hatten oder die Kreditkarte bereits im ersten Jahr erhalten haben, kontrollieren wir dies noch.

# Kontrolle, ob für jeden account_id 12 monate vorhanden sind
month_counts <- summary_df %>%
  group_by(account_id) %>%
  summarise(month_count = n_distinct(month))

# Prüfe, ob jedes account_id 12 Monate hat
month_counts <- month_counts %>% filter(month_count != 12)
month_counts
NA

162 Kunden haben also keine 12 kontinuierlichen Monate mit Transaktionen, bevor sie eine Karte bekommen. Wir filtern diese Kunden raus.

summary_df <- subset(summary_df, !account_id %in% month_counts$account_id)

Als nächstes nummerieren wir die Monate pro account_id von 1 bis 12 durch, um danach weiter damit arbeiten zu können.

# Sortieren nach account_id und Monat
summary_df <- summary_df[order(summary_df$account_id, rev(summary_df$month)),]

# Hinzufügen der Monatsnummer
summary_df$group_id <- ave(seq_along(summary_df$account_id), summary_df$account_id, FUN = function(x) {x})
summary_df$month_number <- 12

for (i in 2:nrow(summary_df)) {
  if (summary_df$account_id[i] != summary_df$account_id[i-1]) {
    summary_df$month_number[i] <- 12
  } else {
    summary_df$month_number[i] <- summary_df$month_number[i-1] - 1
  }
}

# Entferne die Spalte group_id
summary_df$group_id <- NULL
summary_df$month <- NULL

Nun möchten wir alle Informationen pro account_id auf einer Zeile haben. Dafür brauchen wir pivot_wider. So haben wir jede Kennzahl zwölf mal als Kolonne, jedes Mal mit der vorher erstellten Monatsnummer als Suffix.

summary_df_buyers <- summary_df %>%
  group_by(account_id) %>%
  pivot_wider(names_from = month_number,
              values_from = c(max_difference, min_difference, max_balance, min_balance, initial_balance, end_balance, mean_balance, median_balance, std_balance, median_balance, std_balance, mean_difference, median_difference, std_difference, count_positive_difference, count_negative_difference))

summary_df_buyers <- merge(summary_df_buyers, card_buyers, by = "account_id")
summary_df_buyers

Finden von ähnlichen Nutzern

Zu jedem Kartenkäufer soll nun ein ähnlicher Nichtkäufer gefunden werden

# Erstelle ein leeres DataFrame "similar_non_buyers"
similar_non_buyers <- data.frame()

# Iteriere über jeden Kunden im DataFrame "buyers"
for (i in 1:nrow(card_buyers)) {
  # Wähle den aktuellen Kunden aus dem DataFrame "buyers"
  current_buyer <- card_buyers[i, ]
  
  # Wähle die Kunden aus dem DataFrame "non_buyers" aus, die das gleiche Geschlecht haben und möglichst gleich alt sind und möglichst in der gleichen Region wohnen
  similar_non_buyers_temp <- non_buyers %>%
    filter(gender == current_buyer$gender,
           abs(age - current_buyer$age) <= 5,
           region == current_buyer$region)
  
  # Wähle den am besten passenden Kunden aus "similar_non_buyers_temp" aus
  best_match_index <- which.min(abs(similar_non_buyers_temp$age - current_buyer$age))
  best_match <- similar_non_buyers_temp[best_match_index, ]
  best_match$issued <- current_buyer$issued
  
  # damit nicht der gleiche non_buyer doppelt verwendet wird
  non_buyers <- non_buyers %>% filter(client_id != best_match$client_id)
  
  
  similar_non_buyers <- rbind(similar_non_buyers, best_match)
  
}

Zusammenfassen der Transaktionen für non buyers

Auch hier sollen die Transaktionen gleich wie bei den Käufern zusammengefasst werden.

account_ids <- similar_non_buyers$account_id
non_buyer_transactions <- transactions[transactions$account_id %in% account_ids,]

non_buyer_transactions <- merge(non_buyer_transactions, similar_non_buyers[, c("account_id", "issued")], by="account_id")
filtered_df <- non_buyer_transactions %>%
  filter(date >= as.Date(paste0(format(issued - months(13), "%Y-%m"), "-01")) &
         date <= as.Date(paste0(format(issued - months(1), "%Y-%m"), "-01")) - 1)
summary_df <- filtered_df %>%
  group_by(account_id, month = format(date, "%Y-%m")) %>%
  summarise(
    max_difference = max(difference),
    min_difference = min(difference),
    max_balance = max(balance),
    min_balance = min(balance),
    initial_balance = first(balance),
    end_balance = last(balance),
    mean_balance = mean(balance),
    median_balance = median(balance),
    std_balance = sd(balance),
    mean_difference = mean(difference),
    median_difference = median(difference),
    std_difference = sd(difference),
    count_positive_difference = sum(difference > 0),
    count_negative_difference = sum(difference < 0)
  )
summary_df <- summary_df %>%
  arrange(account_id)
# Kontrolle, ob für jeden account_id 12 monate vorhanden sind
month_counts <- summary_df %>%
  group_by(account_id) %>%
  summarise(month_count = n_distinct(month))

# Prüfe, ob jedes account_id 12 Monate hat
month_counts <- month_counts %>% filter(month_count != 12)
month_counts
NA

Auch hier haben wieder einige Kunden weniger als 12 kontinuierliche Monate.

summary_df <- subset(summary_df, !account_id %in% month_counts$account_id)
summary_df <- summary_df[order(summary_df$account_id, rev(summary_df$month)),]
summary_df$group_id <- ave(seq_along(summary_df$account_id), summary_df$account_id, FUN = function(x) {x})

summary_df$month_number <- 12

for (i in 2:nrow(summary_df)) {
  if (summary_df$account_id[i] != summary_df$account_id[i-1]) {
    summary_df$month_number[i] <- 12
  } else {
    summary_df$month_number[i] <- summary_df$month_number[i-1] - 1
  }
}

# Entferne die Spalte group_id
summary_df$group_id <- NULL
summary_df$month <- NULL
summary_df_non_buyers <- summary_df %>%
  group_by(account_id) %>%
  pivot_wider(names_from = month_number,
              values_from = c(max_difference, min_difference, max_balance, min_balance, initial_balance, end_balance, mean_balance, median_balance, std_balance, median_balance, std_balance, mean_difference, median_difference, std_difference, count_positive_difference, count_negative_difference))

Die Transaktionsdaten werden mit den anderen Daten zusammengefügt, um pro Kunde eine Zeile in einem Dataframe zu haben.


summary_df_non_buyers <- merge(summary_df_non_buyers, similar_non_buyers, by = "account_id")
merge(summary_df_non_buyers, non_buyers, by = "account_id")
final_df <- rbind(summary_df_buyers, summary_df_non_buyers)

Jetzt muss noch dass issued-Datum sowie weitere Variabeln entfernt werden.

# Entferne weitere unnötige Variabeln wie ID's oder Werte, welche überall gleich sind
final_df <- final_df %>% select(-client_id, -district_id, -district_id.accounts, -disp_id, -type, -loan_id)

Train-Test-Split

Als Vorbereitung für die Modelle müssen wir unsere Daten zu Trainings- und Testdaten unterteilen. Wir nehmen 80% als Trainingsdaten

set.seed(123)
split <- createDataPartition(final_df$has_card, p = 0.8, list = FALSE)
train <- final_df[split, ]
test <- final_df[-split, ]
LS0tDQp0aXRsZTogImFtbCINCnN1YnRpdGxlOiAiTWluaS1DaGFsbGVuZ2UgMSINCmF1dGhvcjogIlBhc2NhbCBCZXJnZXIgdW5kIFJhcGhhZWwgU3RyZWJlbCINCmRhdGU6ICIxOC4gT2t0b2JlciAyMDIyIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogNA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogdHJ1ZQ0KICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQ0KICAgIHRoZW1lOiB1bml0ZWQNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NClItVmVyc2lvbjogKipbRGVmYXVsdF0gWzY0LWJpdF0gQzpcXFByb2dyYW0gRmlsZXNcXFJcXFItNC4xLjAqKg0KDQojIEltcG9ydHMNCg0KYGBge3IgZWNobz1GQUxTRSwgY2FjaGU9RkFMU0UsIHJlc3VsdHM9RkFMU0UsIGNvbW1lbnQ9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIGNsZWFyIGVudmlyb25tZW50DQpybShsaXN0ID0gbHMoKSkNCg0KIyBuw7Z0aWdlIFBhY2tldGUNCnBhY2thZ2VzIDwtIGMoInRpZHl2ZXJzZSIsICJkYXRhLnRhYmxlIiwgInRpZHltb2RlbHMiLCAibHVicmlkYXRlIikNCg0KIyBOb2NoIG5pY2h0IGluc3RhbGxpZXJ0ZSBQYWtldGUgaW5zdGFsbGllcmVuDQppbnN0YWxsZWRfcGFja2FnZXMgPC0gcGFja2FnZXMgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkNCg0KaWYgKGFueShpbnN0YWxsZWRfcGFja2FnZXMgPT0gRkFMU0UpKSB7DQogIGluc3RhbGwucGFja2FnZXMocGFja2FnZXNbIWluc3RhbGxlZF9wYWNrYWdlc10pDQp9DQojIExhZGVuIGRlciBQYWNrZXRlDQppbnZpc2libGUobGFwcGx5KHBhY2thZ2VzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKQ0KDQojIGNoYW5nZSBvcHRpb25zDQpvcHRpb25zKGRwbHlyLnN1bW1hcmlzZS5pbmZvcm0gPSBGQUxTRSkNCmBgYA0KDQpJbiBmb2xnZW5kZW0gSnVuayB3ZXJkZW4gYWxsZSBUYWJlbGxlbiBhdXMgZGVuIENTVidzIGVpbmdlbGVzZW4uDQpEb2t1IERhdGVuOiBodHRwczovL3NvcnJ5LnZzZS5jei9+YmVya2EvY2hhbGxlbmdlL1BBU1QvaW5kZXguaHRtbA0KKHJlY2h0ZSBTZWl0ZSBQS0REJzk5IENoYWxsZW5nZSA+IERhdGEgPiBGaW5hbmNpYWwgRGF0YSBEZXNjcmlwdGlvbikNCg0KDQojIEVpbmxlc2VuIGRlciBEYXRlbg0KDQpEZXIgRGF0ZW5zYXR6IGJlc3RlaHQgYXVzIGFjaHQgdmVyc2NoaWVkZW5lbiBUYWJlbGxlbiwgd2VsY2hlIHRlaWxzIGR1cmNoIEtleXMgbWl0ZWluYW5kZXIgdmVya27DvHBmdCBzaW5kLg0KYGBge3J9DQpyb290X3BhdGggPC0gIi4veHNlbGxpbmdfYmFua2luZ19kYXRhLTEveHNlbGxpbmdfYmFua2luZ19kYXRhLyINCg0KYWNjb3VudHMgPC0gcmVhZC5jc3YocGFzdGUwKHJvb3RfcGF0aCwgImFjY291bnQuY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCmNhcmRzIDwtIHJlYWQuY3N2KHBhc3RlMChyb290X3BhdGgsICJjYXJkLmNzdiIpLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiOyIpDQpjbGllbnRzIDwtIHJlYWQuY3N2KHBhc3RlMChyb290X3BhdGgsICJjbGllbnQuY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCmRpc3Bvc2l0aW9ucyA8LSByZWFkLmNzdihwYXN0ZTAocm9vdF9wYXRoLCAiZGlzcC5jc3YiKSwgaGVhZGVyID0gVFJVRSwgc2VwID0gIjsiKQ0KZGlzdHJpY3RzIDwtIHJlYWQuY3N2KHBhc3RlMChyb290X3BhdGgsICJkaXN0cmljdC5jc3YiKSwgc2VwID0gIjsiKQ0KbG9hbnMgPC0gcmVhZC5jc3YocGFzdGUwKHJvb3RfcGF0aCwgImxvYW4uY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCm9yZGVycyA8LSByZWFkLmNzdihwYXN0ZTAocm9vdF9wYXRoLCAib3JkZXIuY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCnRyYW5zYWN0aW9ucyA8LSByZWFkLmNzdihwYXN0ZTAocm9vdF9wYXRoLCAidHJhbnMuY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCmBgYA0KDQojIENsZWFuaW5nDQoNCiMjIEFjY291bnRzDQpgYGB7cn0NCnNhbXBsZV9uKGFjY291bnRzLCA1KQ0KYGBgDQpEaWUgQWNjb3VudC1UYWJlbGxlIGVudGjDpGx0IHZpZXIgS29sb25uZW46IGRpZSBBY2NvdW50LUlELCBkaWUgRGlzdHJpY3QtSUQgKHdlbGNoZSBhdWYgZGllIERpc3RyaWN0LVRhYmVsbGUgdmVyd2Vpc3QpLCBkaWUgRnJlcXVlbnosIHdlbGNoZSBkaWUgSMOkdWZpZ2tlaXQgZGVyIEF1c3N0ZWxsdW5nIGRlciBBYnJlY2hudW5nZW4gYWxzIEthdGVnb3JpZSBiZXNhZ3QsIHVuZCBkYXMgRXJzdGVsbHVuZ3NkYXR1bSBkZXMgQWNjb3VudHMuIERpZSBGcmVxdWVueiBrYW5uIGVpbmUgdm9uIGRyZWkgdmVyc2NoaWVkZW5lbiBXZXJ0ZW4gYW5uZWhtZW4uDQoNCmBgYHtyfQ0KdW5pcXVlKGFjY291bnRzJGZyZXF1ZW5jeSkNCmBgYA0KDQpOYWNoZm9sZ2VuZCBzb2xsZW4gZGllIEZyZXF1ZW56LVdlcnRlIMO8YmVyc2V0enQgdW5kIGRhcyBEYXR1bSBpbiBlaW4gcmljaHRpZ2VzIEZvcm1hdCB0cmFuc2Zvcm1pZXJ0IHdlcmRlbi4gQXVzc2VyZGVtIHNvbGwgZGllIFRhYmVsbGUgYXVmIGZlaGxlbmRlIFdlcnRlIMO8YmVycHLDvGZ0IHdlcmRlbi4NCg0KYGBge3J9DQphY2NvdW50cyRkYXRlIDwtIGFzLkRhdGUoYXMuY2hhcmFjdGVyKGFjY291bnRzJGRhdGUpLCBmb3JtYXQ9ICIleSVtJWQiKQ0KDQphY2NvdW50cyRmcmVxdWVuY3lbYWNjb3VudHMkZnJlcXVlbmN5ID09ICJQT1BMQVRFSyBNRVNJQ05FIl0gICA8LSAibW9udGhseSINCmFjY291bnRzJGZyZXF1ZW5jeVthY2NvdW50cyRmcmVxdWVuY3kgPT0gIlBPUExBVEVLIFRZRE5FIl0gICAgIDwtICJ3ZWVrbHkiDQphY2NvdW50cyRmcmVxdWVuY3lbYWNjb3VudHMkZnJlcXVlbmN5ID09ICJQT1BMQVRFSyBQTyBPQlJBVFUiXSA8LSAiYWZ0ZXJfdHJhbnNhY3Rpb24iDQoNCnN1bShpcy5uYShhY2NvdW50cykpDQpgYGANCg0KRXMgZ2lidCBhbHNvIGtlaW5lIGZlaGxlbmRlIFdlcnRlIGluIGRpZXNlbSBEYXRhZnJhbWUuDQoNCiMjIENhcmRzDQoNCmBgYHtyfQ0Kc2FtcGxlX24oY2FyZHMsIDUpDQpgYGANCkF1Y2ggYmVpIGNhcmQgbXVzcyBkYXMgRGF0dW0gdW1nZXdhbmRlbHQgd2VyZGVuLCBkZXIgemVpdGxpY2hlIFRlaWwgd2lyZCBpZ25vcmllcnQsIGRhIGVyIGltbWVyIDAgaXN0Lg0KDQpgYGB7cn0NCmNhcmRzJGlzc3VlZCA8LSBhcy5EYXRlKGFzLmNoYXJhY3RlcihjYXJkcyRpc3N1ZWQpLCBmb3JtYXQ9ICIleSVtJWQiKQ0KDQpjYXJkcyR0eXBlW2NhcmRzJHR5cGUgPT0gImdvbGQiXSAgICAgPC0gImNsYXNzaWMiDQpjYXJkcyA8LSBmaWx0ZXIoY2FyZHMsIHR5cGUgPT0gImNsYXNzaWMiKQ0KDQpzdW0oaXMubmEoY2FyZHMpKQ0KYGBgDQoNCiMjIENsaWVudHMNCg0KYGBge3J9DQpzYW1wbGVfbihjbGllbnRzLCAxMCkNCmBgYA0KDQpJbiBkZXIgVGFiZWxsZSBleGlzdGllcnQgZGllIFNwYWx0ZSBiaXJ0aF9udW1iZXIsIHdlbGNoZXIgbWFuIGF1ZiBkZW4gZXJzdGVuIEJsaWNrIGRpZSBEYXR1bXNyw6RwcmVzZW50YXRpb24gbmljaHQgYW5zaWVodC4NCkluIGRlciBEb2t1IHdpcmQgZGllIFN0cnVrdHVyIGRldXRsaWNoLCBzaWUgaXN0IGbDvHIgTcOkbm5lciBZWU1NREQgdW5kIGbDvHIgRnJhdWVuIFlZTU1ERCs1MERELg0KSW4gRm9sZ2Ugd2lyZCBkaWUgTnVtbWVyIGluIGlocmUgRGF0dW1zcsOkcHJlc2VudGF0aW9uIGtvbnZlcnRpZXJ0IHVuZCBkaWUgU3BhbHRlICJnZW5kZXIiIGFscyBtYWxlL2ZlbWFsZSBhdWZnZXNjaGzDvHNzZWx0Lg0KWnVkZW0gd2lyZCBkYXMgQWx0ZXIgZGVyIGNsaWVudHMgYmV6b2dlbiBhdWYgZGFzIEphaHIgMTk5OSBoZXJhdXNleHRyYWhpZXJ0LCBkYSBkZXIgRGF0ZW5zYXR6IGF1cyBkaWVzZW0gSmFociBzdGFtbXQuDQpFcyB3aXJkIG5pY2h0IHllYXIoU3lzLkRhdGUoKSkgdmVyd2VuZGV0LCBkYW1pdCBkaWUgRGF0ZW4gYXVjaCBpbiBadWt1bmZ0IGtvbnNpc3RlbnQgYmxpZWJlbi4NCg0KDQoNCmBgYHtyfQ0KIyBNb250aHMgYWJvdmUgMTIgbXVzdCBiZSBmZW1hbGUNCmNsaWVudHMgPC0gbXV0YXRlKGNsaWVudHMsIGdlbmRlciA9IA0KICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHN1YnN0cihiaXJ0aF9udW1iZXIsIDMsIDQpID4gMTIsICJmZW1hbGUiLCAibWFsZSIpKQ0KDQojIFN1YnN0cmFjdCB0aGUgNTAgdG8gZ2V0IHRoZSBiaXJ0aCBtb250aA0KY2xpZW50cyA8LSBtdXRhdGUoY2xpZW50cywgYmlydGhfbW9udGggPQ0KICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGFzLm51bWVyaWMoc3Vic3RyKGJpcnRoX251bWJlciwgMywgNCkpID4gMTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMubnVtZXJpYyhzdWJzdHIoYmlydGhfbnVtYmVyLCAzLCA0KSkgLSA1MCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5udW1lcmljKHN1YnN0cihiaXJ0aF9udW1iZXIsIDMsIDQpKSkpDQoNCiMgVHJhbnNmb3JtIHRoZSBiaXJ0aF9udW1iZXIgdG8gYSBkYXRlDQpjbGllbnRzIDwtIG11dGF0ZShjbGllbnRzLCBiaXJ0aF9udW1iZXIgPSBwYXN0ZSgiMTkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3Vic3RyKGJpcnRoX251bWJlciwgMSwgMiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX3BhZChiaXJ0aF9tb250aCwgMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkID0gIjAiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YnN0cihiaXJ0aF9udW1iZXIsIDUsIDYpLA0KICAgICAgICAgICAgICAgICAgIHNlcCA9ICIiLCBjb2xsYXBzZSA9IE5VTEwpKQ0KY2xpZW50cyRiaXJ0aF9kYXRlIDwtIGFzLkRhdGUoYXMuY2hhcmFjdGVyKGNsaWVudHMkYmlydGhfbnVtYmVyKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQ9ICIlWSVtJWQiKQ0KDQojIFJlbW92ZSB1bnVzZWQgY29sdW1ucw0KY2xpZW50cyRiaXJ0aF9tb250aCA8LSBOVUxMDQpjbGllbnRzJGJpcnRoX251bWJlciA8LSBOVUxMDQoNCiMgR2V0IHRoZSBhZ2Ugb2YgdGhlIGNsaWVudHMgaW4gdGhlIHllYXIgMTk5OSBhbmQgc2F2ZSBpdCBpbiBhIGNvbHVtbg0KZ2V0X2FnZSA8LSBmdW5jdGlvbihiaXJ0aF9kYXRlKSB7DQogIGJhc2VfeWVhciA8LSA5OQ0KICB5ZWFyIDwtIHN1YnN0cihiaXJ0aF9kYXRlLCAzLCA0KQ0KICByZXN1bHQgPC0gYmFzZV95ZWFyIC0gYXMuaW50ZWdlcih5ZWFyKQ0KICANCiAgcmV0dXJuKHJlc3VsdCkNCn0NCmNsaWVudHMgPC0gY2xpZW50cyAlPiUNCiAgIG11dGF0ZShhZ2UgPSBnZXRfYWdlKGJpcnRoX2RhdGUpKQ0KDQpzdW0oaXMubmEoY2xpZW50cykpDQpgYGANCmBgYHtyfQ0KYWNjb3VudHMNCmNsaWVudHMNCmBgYA0KDQoNCg0KIyMgRGlzcG9zaXRpb25zDQoNCmBgYHtyfQ0Kc2FtcGxlX24oZGlzcG9zaXRpb25zLCA1KQ0KYGBgDQoNCkJlaSBEaXNwb3NpdGlvbnMgc29sbGVuIG51ciBPd25lcnMgdmVyd2VuZGV0IHdlcmRlbiwgZGEgZGllIEFuYWx5c2UgbnVyIEVpZ2VudMO8bWVyIHZvbiBLb250ZW4gYmVoYW5kZWxuIHNvbGwuDQoNCmBgYHtyfQ0KZGlzcG9zaXRpb25zIDwtIGRpc3Bvc2l0aW9ucyAlPiUgZmlsdGVyKHR5cGUgPT0gJ09XTkVSJykNCg0Kc3VtKGlzLm5hKGRpc3Bvc2l0aW9ucykpDQpgYGANCg0KIyMgRGlzdHJpY3RzDQoNCkJlaSBkaXN0cmljdCBzaW5kIGRpZSBTcGFsdGVubmFtZW4gZGVyIFRhYmVsbGUgYWJoYW5kZW4gZ2Vrb21tZW4uDQpIaWVyIHdlcmRlbiBkaWUgVGFiZWxsZW5uYW1lbiB1bWJlbmFubnQsIGdlbcOkc3MgRG9rdS4NCg0KYGBge3J9DQpkaXN0cmljdHMgPC0gcmVuYW1lKGRpc3RyaWN0cywgZGlzdHJpY3RfaWQgPSBBMSwgZGlzdHJpY3RfbmFtZSA9IEEyLCByZWdpb24gPSBBMywgDQogICAgICAgICAgICAgICAgICAgaW5oYWJpdGFudHMgPSBBNCwgbXVuaWNpcGFsaXRpZXNfaW5oYWJpdGFudHNfc21hbGxlcl80OTkgPSBBNSwgDQogICAgICAgICAgICAgICAgICAgbXVuaWNpcGFsaXRpZXNfaW5oYWJpdGFudHNfNTAwX3RvXzE5OTkgPSBBNiwgDQogICAgICAgICAgICAgICAgICAgbXVuaWNpcGFsaXRpZXNfaW5oYWJpdGFudHNfMjAwMF90b185OTk5ID0gQTcsIA0KICAgICAgICAgICAgICAgICAgIG11bmljaXBhbGl0aWVzX2luaGFiaXRhbnRzX2xhcmdlcl8xMDAwMCA9IEE4LCBjaXRpZXMgPSBBOSwgDQogICAgICAgICAgICAgICAgICAgdXJiYW5faW5oYWJpdGFudHNfcmF0aW8gPSBBMTAsIGF2ZXJhZ2Vfc2FsYXJ5ID0gQTExLA0KICAgICAgICAgICAgICAgICAgIHVuZW1wbG95bWVudF9yYXRlXzk1ID0gQTEyLCB1bmVtcGxveW1lbnRfcmF0ZV85NiA9IEExMywNCiAgICAgICAgICAgICAgICAgICBlbnRyZXByZW5ldXJzX3Blcl8xMDAwID0gQTE0LCBjcmltZXNfOTUgPSBBMTUsDQogICAgICAgICAgICAgICAgICAgY3JpbWVzXzk2ID0gQTE2KQ0KDQpzdW0oaXMubmEoZGlzdHJpY3RzKSkNCmBgYA0KDQojIyBUcmFuc2FjdGlvbnMNCg0KYGBge3J9DQpzYW1wbGVfbih0cmFuc2FjdGlvbnMsIDUpDQpgYGANCg0KDQpJbiBkZW4gVHJhbnNha3Rpb25lbiBtdXNzIGRhcyBEYXR1bSBnZW3DpHNzIEZvcm1hdCBZWU1NREQga29udmVydGllcnQgd2VyZGVuLg0KDQpgYGB7cn0NCiMgUmVuYW1lIGtfc3ltYm9sDQp0cmFuc2FjdGlvbnMgPC0gcmVuYW1lKHRyYW5zYWN0aW9ucywgYygiY2hhcmFjdGVyaXphdGlvbiIgPSAia19zeW1ib2wiKSkgDQoNCiMgQ2hhbmdlIGZvcm1hdHMNCnRyYW5zYWN0aW9ucyRkYXRlIDwtIGFzLkRhdGUoYXMuY2hhcmFjdGVyKHRyYW5zYWN0aW9ucyRkYXRlKSwgZm9ybWF0PSAiJXklbSVkIikNCnRyYW5zYWN0aW9ucyRhbW91bnQgPC0gYXMubnVtZXJpYyh0cmFuc2FjdGlvbnMkYW1vdW50KQ0KdHJhbnNhY3Rpb25zJGJhbGFuY2UgPC0gYXMubnVtZXJpYyh0cmFuc2FjdGlvbnMkYmFsYW5jZSkNCg0KIyBUcmFuc2xhdGUgdmFsdWVzDQp0cmFuc2FjdGlvbnMkdHlwZVt0cmFuc2FjdGlvbnMkdHlwZSA9PSAiUFJJSkVNIl0gPC0gImNyZWRpdCINCnRyYW5zYWN0aW9ucyR0eXBlW3RyYW5zYWN0aW9ucyR0eXBlID09ICJWWURBSiJdICA8LSAid2l0aGRyYXdhbCINCnRyYW5zYWN0aW9ucyR0eXBlW3RyYW5zYWN0aW9ucyR0eXBlID09ICJWWUJFUiJdICA8LSAid2l0aGRyYXdhbCINCg0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICJWS0xBRCJdICAgICAgICAgIDwtICJjYXNoIGNyZWRpdCINCnRyYW5zYWN0aW9ucyRvcGVyYXRpb25bdHJhbnNhY3Rpb25zJG9wZXJhdGlvbiA9PSAiUFJFVk9EIFogVUNUVSJdICA8LSAiY29sbGVjdGlvbiINCnRyYW5zYWN0aW9ucyRvcGVyYXRpb25bdHJhbnNhY3Rpb25zJG9wZXJhdGlvbiA9PSAiVllCRVIiXSAgICAgICAgICA8LSAiY2FzaCB3aXRoZHJhd2FsIg0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICIgIl0gICAgICAgICAgICAgIDwtICJ1bmtub3duIg0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICJQUkVWT0QgTkEgVUNFVCJdIDwtICJyZW1pdHRhbmNlIg0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICJWWUJFUiBLQVJUT1UiXSAgIDwtICJjYXJkIHdpdGhkcmF3YWwiDQoNCnRyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uW3RyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uID09ICIgIl0gPC0gInVua25vd24iDQp0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvblt0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvbiA9PSAiRFVDSE9EIl0gPC0gInBlbnNpb24iDQp0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvblt0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvbiA9PSAiVVJPSyJdIDwtICJpbnRlcmVzdCINCnRyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uW3RyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uID09ICJTSVBPIl0gPC0gImhvdXNlaG9sZCINCnRyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uW3RyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uID09ICJTTFVaQlkiXSA8LSAicGF5bWVudCBzdGF0ZW1lbnQiDQp0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvblt0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvbiA9PSAiUE9KSVNUTkUiXSA8LSAiaW5zdXJhbmNlIg0KdHJhbnNhY3Rpb25zJGNoYXJhY3Rlcml6YXRpb25bdHJhbnNhY3Rpb25zJGNoYXJhY3Rlcml6YXRpb24gPT0gIlNBTktDLiBVUk9LIl0gIDwtICJuZWdfaW50ZXJlc3QiDQp0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvblt0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvbiA9PSAiVVZFUiJdICA8LSAibG9hbl9wYXkiDQoNCnN1bShpcy5uYSh0cmFuc2FjdGlvbnMpKQ0KYGBgDQoNCiMjIE9yZGVycw0KDQpgYGB7cn0NCnNhbXBsZV9uKG9yZGVycywgNSkNCmBgYA0KDQpgYGB7cn0NCiMgUmVuYW1lIGNvbHVtbiBrX3N5bWJvbA0Kb3JkZXJzIDwtIHJlbmFtZShvcmRlcnMsICJjaGFyYWN0ZXJpemF0aW9uIiA9ICJrX3N5bWJvbCIpIA0KDQojIFRyYW5zbGF0ZSBjb2x1bW4gY2hhcmFjdGVyaXphdGlvbg0Kb3JkZXJzJGNoYXJhY3Rlcml6YXRpb25bb3JkZXJzJGNoYXJhY3Rlcml6YXRpb24gPT0gIlNJUE8iXSAgICAgPC0gImhvdXNlaG9sZCINCm9yZGVycyRjaGFyYWN0ZXJpemF0aW9uW29yZGVycyRjaGFyYWN0ZXJpemF0aW9uID09ICJVVkVSIl0gICAgIDwtICJsb2FuIg0Kb3JkZXJzJGNoYXJhY3Rlcml6YXRpb25bb3JkZXJzJGNoYXJhY3Rlcml6YXRpb24gPT0gIlBPSklTVE5FIl0gPC0gImluc3VyYW5jZSINCm9yZGVycyRjaGFyYWN0ZXJpemF0aW9uW29yZGVycyRjaGFyYWN0ZXJpemF0aW9uID09ICJMRUFTSU5HIl0gIDwtICJsZWFzaW5nIg0KDQojIENhdGVnb3JpemUgTkEgYXMgdW5rbm93bg0Kb3JkZXJzJGNoYXJhY3Rlcml6YXRpb25baXMubmEob3JkZXJzJGNoYXJhY3Rlcml6YXRpb24pXSA8LSAidW5rbm93biINCg0Kb3JkZXJzJGFtb3VudCA8LSBhcy5udW1lcmljKG9yZGVycyRhbW91bnQpDQoNCnN1bShpcy5uYShsb2FucykpDQpgYGANCg0KDQojIyBMb2Fucw0KDQpgYGB7cn0NCnNhbXBsZV9uKGxvYW5zLCA1KQ0KYGBgDQoNCmBgYHtyfQ0KbG9hbnMkZGF0ZSA8LSBhcy5EYXRlKGFzLmNoYXJhY3Rlcihsb2FucyRkYXRlKSwgZm9ybWF0PSAiJXklbSVkIikNCmxvYW5zJHBheW1lbnRzIDwtIGFzLm51bWVyaWMobG9hbnMkcGF5bWVudHMpDQpsb2FucyRhbW91bnQgPC0gYXMubnVtZXJpYyhsb2FucyRhbW91bnQpDQoNCiMgTWFrZSBjb2x1bW4gc3RhdHVzIGh1bWFuIHJlYWRhYmxlDQpsb2FucyRzdGF0dXNbbG9hbnMkc3RhdHVzID09ICJBIl0gPC0gImZpbmlzaGVkX3BheWVkIg0KbG9hbnMkc3RhdHVzW2xvYW5zJHN0YXR1cyA9PSAiQiJdIDwtICJmaW5pc2hlZF9ub3RfcGF5ZWQiDQpsb2FucyRzdGF0dXNbbG9hbnMkc3RhdHVzID09ICJDIl0gPC0gInJ1bm5pbmdfb2siDQpsb2FucyRzdGF0dXNbbG9hbnMkc3RhdHVzID09ICJEIl0gPC0gInJ1bm5pbmdfaW5fZGVidCINCg0Kc3VtKGlzLm5hKGxvYW5zKSkNCmBgYA0KDQoNCiMgWnVzYW1tZW5mw7xnZW4gZGVyIERhdGFmcmFtZXMNCg0KSW4gZGllc2VtIEFic2Nobml0dCB3ZXJkZW4gZGllIHZlcnNjaGllZGVuZW4gVGFiZWxsZW4genVzYW1tZW4gZ2VzZXR6dC4NCkRhYmVpIHdlcmRlbiBsb2FuLCBjYXJkcyB1bmQgZGlzdHJpY3QgaXQgbGVmdCBqb2luIGFuZ2Vow6RuZ3QsIGRhbWl0IGZlaGxlbmRlIFNwYWx0ZW4gbmljaHQgZGVuIERhdGVuc2F0eiB2ZXJrbGVpbmVybi4NCkRpZSBUcmFuc2FrdGlvbnNkYXRlbiB3ZXJkZW4gaGllciBub2NoIG5pY2h0IHp1c2FtbWVuZ2Vmw7xocnQuDQoNCmBgYHtyfQ0KIyBDbGllbnRzIG1pdCBkaXNwb3NpdGlvbnMNCmZ1bGwgPC0gaW5uZXJfam9pbihjbGllbnRzLCBkaXNwb3NpdGlvbnMsIGJ5ID0gImNsaWVudF9pZCIsIHN1ZmZpeCA9IGMoIi5jbGllbnQiLCAiLmRpc3Bvc2l0aW9ucyIpKQ0Kc3VtKGR1cGxpY2F0ZWQoZnVsbCRjbGllbnRfaWQpKQ0KDQojIEZ1bGwgbWl0IGFjY291bnQNCmZ1bGwgPC0gaW5uZXJfam9pbihmdWxsLCBhY2NvdW50cywgYnkgPSAiYWNjb3VudF9pZCIsIHN1ZmZpeCA9IGMoIiIsICIuYWNjb3VudHMiKSkNCnN1bShkdXBsaWNhdGVkKGZ1bGwkYWNjb3VudF9pZCkpDQoNCiMgRnVsbCBtaXQgbG9hbg0Kc3VtKGR1cGxpY2F0ZWQobG9hbnMkYWNjb3VudF9pZCkpDQpmdWxsIDwtIGxlZnRfam9pbihmdWxsLCBsb2FucywgYnkgPSAiYWNjb3VudF9pZCIsIHN1ZmZpeCA9IGMoIiIsICIubG9hbnMiKSkNCg0KIyBGdWxsIG1pdCBjYXJkcw0KZnVsbCA8LSBsZWZ0X2pvaW4oZnVsbCwgY2FyZHMsIGJ5ID0gImRpc3BfaWQiLCBzdWZmaXggPSBjKCIiLCAiLmNhcmRzIikpDQpzdW0oZHVwbGljYXRlZChjYXJkcyRkaXNwX2lkKSkNCg0KIyBEaXN0cmljdCBJbmZvcm1hdGlvbnMgZm9yIGNsaWVudA0KZnVsbCA8LSBsZWZ0X2pvaW4oZnVsbCwgZGlzdHJpY3RzLCBieSA9ICJkaXN0cmljdF9pZCIpDQoNCiMgRGlzdHJpY3QgaW5mb3JtYXRpb25zIGZvciBjYXJkDQpmdWxsIDwtIGxlZnRfam9pbihmdWxsLCBkaXN0cmljdHMsIGJ5ID0gYygiZGlzdHJpY3RfaWQuYWNjb3VudHMiPSJkaXN0cmljdF9pZCIpLCBzdWZmaXggPSBjKCIiLCAiLmFjY291bnRzIikpDQoNCnNhbXBsZV9uKGZ1bGwsIDUpDQpgYGANCg0KSnVnZW5kbGljaGUgdW5kIFBlcnNvbmVuLCB3ZWxjaGUgd8OkaHJlbmQgZGVzIFplaXRyYXVtcyBkZXMgRGF0ZW5zYXR6ZXMgZXJzdCBlcndhY2hzZW4gd29yZGVuIHNpbmQsIHNvbGxlbiBuaWNodCBpbiBkaWUgQXVzd2VydHVuZyBlaW5mbGllc3Nlbi4NCkRhIHNpY2hlciBkZXIgRGF0ZW5zYXR6IMO8YmVyIGVpbmVuIFplaXRyYXVtIHZvbiBzZWNocyBKYWhyZW4gZXJzdHJlY2t0IHdlcmRlbiBhbGxlIENsaWVudHMgasO8bmdlciBhbHMgMjUgSmFocmUgaGVyYXVzZ2VmaWx0ZXJ0Lg0KDQpgYGB7cn0NCmZ1bGwgPC0gZnVsbCAlPiUgZmlsdGVyKGFnZSA+PSAyNSkNCmBgYA0KDQpgYGB7cn0NCmZ1bGwNCmBgYA0KDQoNCg0KQWxzIG7DpGNoc3RlcyB3ZXJkZW4gYWxsZSBaZWlsZW4gbWl0IEtyZWRpdGthcnRlbmvDpHVmZXJuIHZvbiBkZW4gTmljaHQtS8OkdWZlcm4gZ2V0cmVubnQNCg0KDQpgYGB7cn0NCmhhc19jYXJkX2Z1bmN0aW9uIDwtIGZ1bmN0aW9uKHgpIHsNCiAgaWYgKGlzLm5hKHgpKSB7DQogICAgcmV0dXJuKEZBTFNFKQ0KICB9IGVsc2Ugew0KICAgIHJldHVybihUUlVFKQ0KICB9DQp9DQoNCiMgRXJzdGVsbGUgZGllIG5ldWUgU3BhbHRlICJoYXNfY2FyZCIgbWl0IGRlciBhcHBseSgpLUZ1bmt0aW9uIHVuZCBkZXIgb2JlbiBkZWZpbmllcnRlbiBGdW5rdGlvbg0KZnVsbCRoYXNfY2FyZCA8LSBzYXBwbHkoZnVsbFssICJjYXJkX2lkIl0sIGhhc19jYXJkX2Z1bmN0aW9uKQ0KZnVsbCA8LSBmdWxsICU+JSBzZWxlY3QoLWNhcmRfaWQsIC10eXBlLmNhcmRzKQ0KDQpjYXJkX2J1eWVycyA8LSBmdWxsICU+JSBmaWx0ZXIoaGFzX2NhcmQgPT0gVFJVRSkNCg0Kbm9uX2J1eWVycyA8LSBmdWxsICU+JSBmaWx0ZXIoaGFzX2NhcmQgPT0gRkFMU0UpDQpgYGANCg0KSmV0enQga8O2bm5lbiB3aXIgbm9jaCBlaW5pZ2UgVmFyaWFiZWxuIGVudGZlcm5lbiwgd2VsY2hlIGtlaW5lbiBFaW5mbHVzcyBhdWYgZGFzIE1vZGVsbCBoYWJlbiBzb2xsdGVuLiANCg0KIyBBdWZzdW1taWVyZW4gZGVyIFRyYW5zYWt0aW9uZW4NCg0KYGBge3J9DQpzYW1wbGVfbih0cmFuc2FjdGlvbnMsIDUpDQpgYGANCg0KDQpCZWkgZGVuIFRyYW5zYWt0aW9uZW4gaXN0IGpld2VpbHMgZGllIG5ldWUgQmFsYW5jZSB1bmQgZGVyIEJldHJhZyBkZXIgVHJhbnNha3Rpb24gYW5nZWdlYm4uIERhcyBQcm9ibGVtIGRhYmVpIGlzdCwgZGFzcyBhbGxlIEJldHLDpGdlIHBvc2l0aXYgc2luZCwgYXVjaCB3ZW5uIHNpZSBlaWdlbnRsaWNoIGFiZ2V6b2dlbiB3ZXJkZW4uIA0KDQpgYGB7cn0NCmRmIDwtIHRyYW5zYWN0aW9ucw0KDQojIEtvbnZlcnRpZXJlbiBTaWUgZGFzICdkYXRlJy1GZWxkIGluIGVpbiBEYXR1bQ0KZGYkZGF0ZSA8LSBhcy5EYXRlKGRmJGRhdGUpDQoNCiMgU29ydGllcmVuIFNpZSBkYXMgRGF0YWZyYW1lIG5hY2ggTnV0emVyIHVuZCBEYXR1bQ0KZGYgPC0gZGZbb3JkZXIoZGYkYWNjb3VudF9pZCwgZGYkZGF0ZSksIF0NCg0KIyBHcnVwcGllcmVuIFNpZSBkYXMgRGF0YWZyYW1lIG5hY2ggTnV0emVyDQpkZiA8LSBncm91cF9ieShkZiwgYWNjb3VudF9pZCkNCg0KIyBJdGVyaWVyZW4gU2llIMO8YmVyIGplZGVuIE51dHplciB1bmQgYmVhcmJlaXRlbiBTaWUgZGllIFRyYW5zYWt0aW9uZW4NCmRmIDwtIGRmICU+JSANCiAgc3VtbWFyaXplKHRyYW5zYWN0aW9ucyA9IHsNCiAgICAjIEbDvGdlbiBTaWUgZWluZSBTcGFsdGUgbWl0IGRlbSB2b3JoZXJpZ2VuIEtvbnRvc3RhbmQgaGluenUNCiAgICBwcmV2X2JhbGFuY2UgPC0gaWZlbHNlKHJvd19udW1iZXIoKSA9PSAxLCBOQSwgbGFnKGJhbGFuY2UsIG9yZGVyX2J5ID0gZGF0ZSkpDQoNCiAgICAjIEJlcmVjaG5lbiBTaWUgZGVuIFVudGVyc2NoaWVkIHp3aXNjaGVuIGRlbSB2b3JoZXJpZ2VuIEtvbnRvc3RhbmQgdW5kIGRlbSBha3R1ZWxsZW4gS29udG9zdGFuZA0KICAgIGRpZmZlcmVuY2UgPC0gYmFsYW5jZSAtIHByZXZfYmFsYW5jZQ0KDQogICAgIyBGw7xnZW4gU2llIGVpbmUgU3BhbHRlIG1pdCBkZXIgVHJhbnNha3Rpb25zYXJ0IGhpbnp1DQogICAgdHlwZSA8LSAiYWRkIg0KICAgIHR5cGVbZGlmZmVyZW5jZSA8IDBdIDwtICJzdWJ0cmFjdCINCg0KICAgICMgRXJzdGVsbGVuIFNpZSBkYXMgRGF0YWZyYW1lIG1pdCBkZW4gVHJhbnNha3Rpb25lbiBmw7xyIGplZGVuIE51dHplcg0KICAgIHRyYW5zYWN0aW9uc19kZiA8LSBkYXRhLmZyYW1lKGFtb3VudCwgZGF0ZSwgYmFsYW5jZSwgcHJldl9iYWxhbmNlLCBkaWZmZXJlbmNlLCB0eXBlKQ0KICAgIHRyYW5zYWN0aW9uc19kZg0KICB9KSAlPiUNCiAgdW5ncm91cCgpDQoNCnRyYW5zYWN0aW9ucyA8LSB1bm5lc3QoZGYsIHRyYW5zYWN0aW9ucykNCg0KIyBIaW56dWbDvGdlbiBkZXMgZXJzdGVuIGFtb3VudHMgYmVpIGplZGVtIEFjY291bnQNCnRyYW5zYWN0aW9ucyRkaWZmZXJlbmNlIDwtIGlmZWxzZShpcy5uYSh0cmFuc2FjdGlvbnMkZGlmZmVyZW5jZSkgJiBpcy5uYSh0cmFuc2FjdGlvbnMkcHJldl9iYWxhbmNlKSAmICh0cmFuc2FjdGlvbnMkYW1vdW50ID09IHRyYW5zYWN0aW9ucyRiYWxhbmNlKSwgdHJhbnNhY3Rpb25zJGFtb3VudCwgdHJhbnNhY3Rpb25zJGRpZmZlcmVuY2UpDQoNCnRyYW5zYWN0aW9ucyRhbW91bnQgPC0gTlVMTA0KDQp0cmFuc2FjdGlvbnMNCmBgYA0KDQojIyBadXNhbW1lbmZhc3NlbiBkZXIgVHJhbnNha3Rpb25lbiBmw7xyIENhcmQgQnV5ZXJzDQoNClVtIGRpZSBUcmFuc2FrdGlvbnMtRGF0ZW4gaW4gdW5zZXJlbiBNb2RlbGxlbiBicmF1Y2hlbiB6dSBrw7ZubmVuLCBtdXNzIGbDvHIgamVkZW4gS3VuZGUgZWluIFJvbGx1cC1GZW5zdGVyIGVyc3RlbGx0IHdlcmRlbi4gRGllcyBmYXNzdCBkaWUgVHJhbnNha3Rpb25lbiBkZXIgenfDtmxmIE1vbmF0ZSB2b3IgZGVtIEVyaGFsdCBlaW5lciBLcmVkaXRrYXJ0ZSB6dXNhbW1lbiAobWludXMgZWluZW4gTW9uYXQgSW5wdXQgTGFnKS4gQXVmIGRpZXNlbiBNb25hdGVuIHdlcmRlbiBkaWUgVHJhbnNha3Rpb25lbiB6dXNhbW1lbmdlZmFzc3QuDQoNCkFscyBlcnN0ZXMgd2VyZGVuIGRpZSBUcmFuc2FrdGlvbmVuIHZvbiBLdW5kZW4gaGVyYXVzZ2VmaWx0ZXJ0LCB3ZWxjaGUgZWluZSBLcmVkaXRrYXJ0ZSBoYWJlbi4NCg0KYGBge3J9DQphY2NvdW50X2lkcyA8LSBjYXJkX2J1eWVycyRhY2NvdW50X2lkDQpidXllcl90cmFuc2FjdGlvbnMgPC0gdHJhbnNhY3Rpb25zW3RyYW5zYWN0aW9ucyRhY2NvdW50X2lkICVpbiUgYWNjb3VudF9pZHMsXQ0KYGBgDQoNCkRhcyBpc3N1ZWQtRGF0dW0gc29sbCB6dSBkZW4gVHJhbnNha3Rpb25lbiBoaW56dWdlZsO8Z3Qgd2VyZGVuLCBkYW1pdCBkaWVzZSBmw7xyIGplZGVuIEt1bmRlbiBlaW56ZWxuIGdlZmlsdGVydCB3ZXJkZW4ga8O2bm5lbi4NCg0KYGBge3J9DQpidXllcl90cmFuc2FjdGlvbnMgPC0gbWVyZ2UoYnV5ZXJfdHJhbnNhY3Rpb25zLCBmdWxsWywgYygiYWNjb3VudF9pZCIsICJpc3N1ZWQiKV0sIGJ5PSJhY2NvdW50X2lkIikNCmBgYA0KDQpOdW4gc29sbGVuIFRyYW5zYWt0aW9uZW4gc28gZ2VmaWx0ZXJ0IHdlcmRlbiwgZGFzcyBudXIgbm9jaCBUcmFuc2FrdGlvbmVuIHp3aXNjaGVuIDEzIE1vbmF0ZW4gdW5kIDEgTW9uYXQgdm9yIGRlbSBJc3N1ZWQgRGF0dW0gdm9ya29tbWVuLg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RmIDwtIGJ1eWVyX3RyYW5zYWN0aW9ucyAlPiUNCiAgZmlsdGVyKGRhdGUgPj0gYXMuRGF0ZShwYXN0ZTAoZm9ybWF0KGlzc3VlZCAtIG1vbnRocygxMyksICIlWS0lbSIpLCAiLTAxIikpICYNCiAgICAgICAgIGRhdGUgPD0gYXMuRGF0ZShwYXN0ZTAoZm9ybWF0KGlzc3VlZCAtIG1vbnRocygxKSwgIiVZLSVtIiksICItMDEiKSkgLSAxKQ0KYGBgDQoNCkF1ZiBkaWVzZW4gRGF0ZW4gd2lyZCBlaW5lIEdydXBwaWVydW5nIGFuaGFuZCBkZXIgYWNjb3VudF9pZCB1bmQgZGVzIE1vbmF0cyBnZW1hY2h0IHdlcmRlbi4gRGllIFdlcnRlIGluIGRpZmZlcmVuY2UgdW5kIGJhbGFuY2Ugd2VyZGVuIHp1IHZlcnNjaGllZGVuZW4gTWV0cmlrZW4genVzYW1tZW5nZWZhc3N0Og0KQXVmIGJlaWRlbiBXZXJ0ZW4gZXJmYXNzZW4gd2lyIGRhcyBNaW5pbXVtLCBkYXMgTWF4aW11bSwgZGVuIER1cmNoc2Nobml0dCwgZGVuIE1lZGlhbiB1bmQgZGllIFN0YW5kYXJkYWJ3ZWljaHVuZy4gQmVpIGRlciBiYWxhbmNlIGVyZmFzc2VuIHdpciBkaWUgZXJzdGUgdW5kIGRpZSBsZXR6dGUgQmFsYW5jZSBkZXMgTW9uYXRzIHVuZCBiZWkgZGlmZmVyZW5jZSBkaWUgQW56YWhsIHBvc2l0aXZlIHVuZCBuZWdhdGl2ZSBkaWZmZXJlbmNlcy4NCg0KYGBge3J9DQpzdW1tYXJ5X2RmIDwtIGZpbHRlcmVkX2RmICU+JQ0KICBncm91cF9ieShhY2NvdW50X2lkLCBtb250aCA9IGZvcm1hdChkYXRlLCAiJVktJW0iKSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBtYXhfZGlmZmVyZW5jZSA9IG1heChkaWZmZXJlbmNlKSwNCiAgICBtaW5fZGlmZmVyZW5jZSA9IG1pbihkaWZmZXJlbmNlKSwNCiAgICBtYXhfYmFsYW5jZSA9IG1heChiYWxhbmNlKSwNCiAgICBtaW5fYmFsYW5jZSA9IG1pbihiYWxhbmNlKSwNCiAgICBpbml0aWFsX2JhbGFuY2UgPSBmaXJzdChiYWxhbmNlKSwNCiAgICBlbmRfYmFsYW5jZSA9IGxhc3QoYmFsYW5jZSksDQogICAgbWVhbl9iYWxhbmNlID0gbWVhbihiYWxhbmNlKSwNCiAgICBtZWRpYW5fYmFsYW5jZSA9IG1lZGlhbihiYWxhbmNlKSwNCiAgICBzdGRfYmFsYW5jZSA9IHNkKGJhbGFuY2UpLA0KICAgIG1lYW5fZGlmZmVyZW5jZSA9IG1lYW4oZGlmZmVyZW5jZSksDQogICAgbWVkaWFuX2RpZmZlcmVuY2UgPSBtZWRpYW4oZGlmZmVyZW5jZSksDQogICAgc3RkX2RpZmZlcmVuY2UgPSBzZChkaWZmZXJlbmNlKSwNCiAgICBjb3VudF9wb3NpdGl2ZV9kaWZmZXJlbmNlID0gc3VtKGRpZmZlcmVuY2UgPiAwKSwNCiAgICBjb3VudF9uZWdhdGl2ZV9kaWZmZXJlbmNlID0gc3VtKGRpZmZlcmVuY2UgPCAwKQ0KICApDQpzdW1tYXJ5X2RmIDwtIHN1bW1hcnlfZGYgJT4lDQogIGFycmFuZ2UoYWNjb3VudF9pZCkNCnN1bW1hcnlfZGYNCmBgYA0KDQpKZXR6dCBoYWJlbiB3aXIgZsO8ciBqZWRlIGFjY291bnRfaWQgZWluZSDDnGJlcnNpY2h0IMO8YmVyIGRpZSAxMiBNb25hdGUgdm9yIGRlbSBLYXJ0ZW5lcmhhbHQuIERhIGVzIGFiZXIgc2VpbiBrw7ZubnRlLCBkYXNzIGVzIEt1bmRlbiBnaWJ0LCB3ZWxjaGUgbmljaHQgamVkZW4gTW9uYXQgZWluZSBUcmFuc2FrdGlvbiBoYXR0ZW4gb2RlciBkaWUgS3JlZGl0a2FydGUgYmVyZWl0cyBpbSBlcnN0ZW4gSmFociBlcmhhbHRlbiBoYWJlbiwga29udHJvbGxpZXJlbiB3aXIgZGllcyBub2NoLg0KDQpgYGB7cn0NCiMgS29udHJvbGxlLCBvYiBmw7xyIGplZGVuIGFjY291bnRfaWQgMTIgbW9uYXRlIHZvcmhhbmRlbiBzaW5kDQptb250aF9jb3VudHMgPC0gc3VtbWFyeV9kZiAlPiUNCiAgZ3JvdXBfYnkoYWNjb3VudF9pZCkgJT4lDQogIHN1bW1hcmlzZShtb250aF9jb3VudCA9IG5fZGlzdGluY3QobW9udGgpKQ0KDQojIFByw7xmZSwgb2IgamVkZXMgYWNjb3VudF9pZCAxMiBNb25hdGUgaGF0DQptb250aF9jb3VudHMgPC0gbW9udGhfY291bnRzICU+JSBmaWx0ZXIobW9udGhfY291bnQgIT0gMTIpDQptb250aF9jb3VudHMNCg0KYGBgDQoxNjIgS3VuZGVuIGhhYmVuIGFsc28ga2VpbmUgMTIga29udGludWllcmxpY2hlbiBNb25hdGUgbWl0IFRyYW5zYWt0aW9uZW4sIGJldm9yIHNpZSBlaW5lIEthcnRlIGJla29tbWVuLiBXaXIgZmlsdGVybiBkaWVzZSBLdW5kZW4gcmF1cy4NCg0KYGBge3J9DQpzdW1tYXJ5X2RmIDwtIHN1YnNldChzdW1tYXJ5X2RmLCAhYWNjb3VudF9pZCAlaW4lIG1vbnRoX2NvdW50cyRhY2NvdW50X2lkKQ0KYGBgDQoNCkFscyBuw6RjaHN0ZXMgbnVtbWVyaWVyZW4gd2lyIGRpZSBNb25hdGUgcHJvIGFjY291bnRfaWQgdm9uIDEgYmlzIDEyIGR1cmNoLCB1bSBkYW5hY2ggd2VpdGVyIGRhbWl0IGFyYmVpdGVuIHp1IGvDtm5uZW4uDQoNCmBgYHtyfQ0KIyBTb3J0aWVyZW4gbmFjaCBhY2NvdW50X2lkIHVuZCBNb25hdA0Kc3VtbWFyeV9kZiA8LSBzdW1tYXJ5X2RmW29yZGVyKHN1bW1hcnlfZGYkYWNjb3VudF9pZCwgcmV2KHN1bW1hcnlfZGYkbW9udGgpKSxdDQoNCiMgSGluenVmw7xnZW4gZGVyIE1vbmF0c251bW1lcg0Kc3VtbWFyeV9kZiRncm91cF9pZCA8LSBhdmUoc2VxX2Fsb25nKHN1bW1hcnlfZGYkYWNjb3VudF9pZCksIHN1bW1hcnlfZGYkYWNjb3VudF9pZCwgRlVOID0gZnVuY3Rpb24oeCkge3h9KQ0Kc3VtbWFyeV9kZiRtb250aF9udW1iZXIgPC0gMTINCg0KZm9yIChpIGluIDI6bnJvdyhzdW1tYXJ5X2RmKSkgew0KICBpZiAoc3VtbWFyeV9kZiRhY2NvdW50X2lkW2ldICE9IHN1bW1hcnlfZGYkYWNjb3VudF9pZFtpLTFdKSB7DQogICAgc3VtbWFyeV9kZiRtb250aF9udW1iZXJbaV0gPC0gMTINCiAgfSBlbHNlIHsNCiAgICBzdW1tYXJ5X2RmJG1vbnRoX251bWJlcltpXSA8LSBzdW1tYXJ5X2RmJG1vbnRoX251bWJlcltpLTFdIC0gMQ0KICB9DQp9DQoNCiMgRW50ZmVybmUgZGllIFNwYWx0ZSBncm91cF9pZA0Kc3VtbWFyeV9kZiRncm91cF9pZCA8LSBOVUxMDQpzdW1tYXJ5X2RmJG1vbnRoIDwtIE5VTEwNCmBgYA0KDQpOdW4gbcO2Y2h0ZW4gd2lyIGFsbGUgSW5mb3JtYXRpb25lbiBwcm8gYWNjb3VudF9pZCBhdWYgZWluZXIgWmVpbGUgaGFiZW4uIERhZsO8ciBicmF1Y2hlbiB3aXIgcGl2b3Rfd2lkZXIuIFNvIGhhYmVuIHdpciBqZWRlIEtlbm56YWhsIHp3w7ZsZiBtYWwgYWxzIEtvbG9ubmUsIGplZGVzIE1hbCBtaXQgZGVyIHZvcmhlciBlcnN0ZWxsdGVuIE1vbmF0c251bW1lciBhbHMgU3VmZml4Lg0KDQpgYGB7cn0NCnN1bW1hcnlfZGZfYnV5ZXJzIDwtIHN1bW1hcnlfZGYgJT4lDQogIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbW9udGhfbnVtYmVyLA0KICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IGMobWF4X2RpZmZlcmVuY2UsIG1pbl9kaWZmZXJlbmNlLCBtYXhfYmFsYW5jZSwgbWluX2JhbGFuY2UsIGluaXRpYWxfYmFsYW5jZSwgZW5kX2JhbGFuY2UsIG1lYW5fYmFsYW5jZSwgbWVkaWFuX2JhbGFuY2UsIHN0ZF9iYWxhbmNlLCBtZWRpYW5fYmFsYW5jZSwgc3RkX2JhbGFuY2UsIG1lYW5fZGlmZmVyZW5jZSwgbWVkaWFuX2RpZmZlcmVuY2UsIHN0ZF9kaWZmZXJlbmNlLCBjb3VudF9wb3NpdGl2ZV9kaWZmZXJlbmNlLCBjb3VudF9uZWdhdGl2ZV9kaWZmZXJlbmNlKSkNCg0Kc3VtbWFyeV9kZl9idXllcnMgPC0gbWVyZ2Uoc3VtbWFyeV9kZl9idXllcnMsIGNhcmRfYnV5ZXJzLCBieSA9ICJhY2NvdW50X2lkIikNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnlfZGZfYnV5ZXJzDQpgYGANCg0KDQojIyBGaW5kZW4gdm9uIMOkaG5saWNoZW4gTnV0emVybg0KDQpadSBqZWRlbSBLYXJ0ZW5rw6R1ZmVyIHNvbGwgbnVuIGVpbiDDpGhubGljaGVyIE5pY2h0a8OkdWZlciBnZWZ1bmRlbiB3ZXJkZW4NCg0KYGBge3J9DQojIEVyc3RlbGxlIGVpbiBsZWVyZXMgRGF0YUZyYW1lICJzaW1pbGFyX25vbl9idXllcnMiDQpzaW1pbGFyX25vbl9idXllcnMgPC0gZGF0YS5mcmFtZSgpDQoNCiMgSXRlcmllcmUgw7xiZXIgamVkZW4gS3VuZGVuIGltIERhdGFGcmFtZSAiYnV5ZXJzIg0KZm9yIChpIGluIDE6bnJvdyhjYXJkX2J1eWVycykpIHsNCiAgIyBXw6RobGUgZGVuIGFrdHVlbGxlbiBLdW5kZW4gYXVzIGRlbSBEYXRhRnJhbWUgImJ1eWVycyINCiAgY3VycmVudF9idXllciA8LSBjYXJkX2J1eWVyc1tpLCBdDQogIA0KICAjIFfDpGhsZSBkaWUgS3VuZGVuIGF1cyBkZW0gRGF0YUZyYW1lICJub25fYnV5ZXJzIiBhdXMsIGRpZSBkYXMgZ2xlaWNoZSBHZXNjaGxlY2h0IGhhYmVuIHVuZCBtw7ZnbGljaHN0IGdsZWljaCBhbHQgc2luZCB1bmQgbcO2Z2xpY2hzdCBpbiBkZXIgZ2xlaWNoZW4gUmVnaW9uIHdvaG5lbg0KICBzaW1pbGFyX25vbl9idXllcnNfdGVtcCA8LSBub25fYnV5ZXJzICU+JQ0KICAgIGZpbHRlcihnZW5kZXIgPT0gY3VycmVudF9idXllciRnZW5kZXIsDQogICAgICAgICAgIGFicyhhZ2UgLSBjdXJyZW50X2J1eWVyJGFnZSkgPD0gNSwNCiAgICAgICAgICAgcmVnaW9uID09IGN1cnJlbnRfYnV5ZXIkcmVnaW9uKQ0KICANCiAgIyBXw6RobGUgZGVuIGFtIGJlc3RlbiBwYXNzZW5kZW4gS3VuZGVuIGF1cyAic2ltaWxhcl9ub25fYnV5ZXJzX3RlbXAiIGF1cw0KICBiZXN0X21hdGNoX2luZGV4IDwtIHdoaWNoLm1pbihhYnMoc2ltaWxhcl9ub25fYnV5ZXJzX3RlbXAkYWdlIC0gY3VycmVudF9idXllciRhZ2UpKQ0KICBiZXN0X21hdGNoIDwtIHNpbWlsYXJfbm9uX2J1eWVyc190ZW1wW2Jlc3RfbWF0Y2hfaW5kZXgsIF0NCiAgYmVzdF9tYXRjaCRpc3N1ZWQgPC0gY3VycmVudF9idXllciRpc3N1ZWQNCiAgDQogICMgZGFtaXQgbmljaHQgZGVyIGdsZWljaGUgbm9uX2J1eWVyIGRvcHBlbHQgdmVyd2VuZGV0IHdpcmQNCiAgbm9uX2J1eWVycyA8LSBub25fYnV5ZXJzICU+JSBmaWx0ZXIoY2xpZW50X2lkICE9IGJlc3RfbWF0Y2gkY2xpZW50X2lkKQ0KICANCiAgDQogIHNpbWlsYXJfbm9uX2J1eWVycyA8LSByYmluZChzaW1pbGFyX25vbl9idXllcnMsIGJlc3RfbWF0Y2gpDQogIA0KfQ0KYGBgDQoNCg0KIyMgWnVzYW1tZW5mYXNzZW4gZGVyIFRyYW5zYWt0aW9uZW4gZsO8ciBub24gYnV5ZXJzDQoNCkF1Y2ggaGllciBzb2xsZW4gZGllIFRyYW5zYWt0aW9uZW4gZ2xlaWNoIHdpZSBiZWkgZGVuIEvDpHVmZXJuIHp1c2FtbWVuZ2VmYXNzdCB3ZXJkZW4uDQoNCmBgYHtyfQ0KYWNjb3VudF9pZHMgPC0gc2ltaWxhcl9ub25fYnV5ZXJzJGFjY291bnRfaWQNCm5vbl9idXllcl90cmFuc2FjdGlvbnMgPC0gdHJhbnNhY3Rpb25zW3RyYW5zYWN0aW9ucyRhY2NvdW50X2lkICVpbiUgYWNjb3VudF9pZHMsXQ0KDQpub25fYnV5ZXJfdHJhbnNhY3Rpb25zIDwtIG1lcmdlKG5vbl9idXllcl90cmFuc2FjdGlvbnMsIHNpbWlsYXJfbm9uX2J1eWVyc1ssIGMoImFjY291bnRfaWQiLCAiaXNzdWVkIildLCBieT0iYWNjb3VudF9pZCIpDQoNCmBgYA0KYGBge3J9DQpmaWx0ZXJlZF9kZiA8LSBub25fYnV5ZXJfdHJhbnNhY3Rpb25zICU+JQ0KICBmaWx0ZXIoZGF0ZSA+PSBhcy5EYXRlKHBhc3RlMChmb3JtYXQoaXNzdWVkIC0gbW9udGhzKDEzKSwgIiVZLSVtIiksICItMDEiKSkgJg0KICAgICAgICAgZGF0ZSA8PSBhcy5EYXRlKHBhc3RlMChmb3JtYXQoaXNzdWVkIC0gbW9udGhzKDEpLCAiJVktJW0iKSwgIi0wMSIpKSAtIDEpDQpgYGANCmBgYHtyfQ0Kc3VtbWFyeV9kZiA8LSBmaWx0ZXJlZF9kZiAlPiUNCiAgZ3JvdXBfYnkoYWNjb3VudF9pZCwgbW9udGggPSBmb3JtYXQoZGF0ZSwgIiVZLSVtIikpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbWF4X2RpZmZlcmVuY2UgPSBtYXgoZGlmZmVyZW5jZSksDQogICAgbWluX2RpZmZlcmVuY2UgPSBtaW4oZGlmZmVyZW5jZSksDQogICAgbWF4X2JhbGFuY2UgPSBtYXgoYmFsYW5jZSksDQogICAgbWluX2JhbGFuY2UgPSBtaW4oYmFsYW5jZSksDQogICAgaW5pdGlhbF9iYWxhbmNlID0gZmlyc3QoYmFsYW5jZSksDQogICAgZW5kX2JhbGFuY2UgPSBsYXN0KGJhbGFuY2UpLA0KICAgIG1lYW5fYmFsYW5jZSA9IG1lYW4oYmFsYW5jZSksDQogICAgbWVkaWFuX2JhbGFuY2UgPSBtZWRpYW4oYmFsYW5jZSksDQogICAgc3RkX2JhbGFuY2UgPSBzZChiYWxhbmNlKSwNCiAgICBtZWFuX2RpZmZlcmVuY2UgPSBtZWFuKGRpZmZlcmVuY2UpLA0KICAgIG1lZGlhbl9kaWZmZXJlbmNlID0gbWVkaWFuKGRpZmZlcmVuY2UpLA0KICAgIHN0ZF9kaWZmZXJlbmNlID0gc2QoZGlmZmVyZW5jZSksDQogICAgY291bnRfcG9zaXRpdmVfZGlmZmVyZW5jZSA9IHN1bShkaWZmZXJlbmNlID4gMCksDQogICAgY291bnRfbmVnYXRpdmVfZGlmZmVyZW5jZSA9IHN1bShkaWZmZXJlbmNlIDwgMCkNCiAgKQ0Kc3VtbWFyeV9kZiA8LSBzdW1tYXJ5X2RmICU+JQ0KICBhcnJhbmdlKGFjY291bnRfaWQpDQpgYGANCg0KYGBge3J9DQojIEtvbnRyb2xsZSwgb2IgZsO8ciBqZWRlbiBhY2NvdW50X2lkIDEyIG1vbmF0ZSB2b3JoYW5kZW4gc2luZA0KbW9udGhfY291bnRzIDwtIHN1bW1hcnlfZGYgJT4lDQogIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JQ0KICBzdW1tYXJpc2UobW9udGhfY291bnQgPSBuX2Rpc3RpbmN0KG1vbnRoKSkNCg0KIyBQcsO8ZmUsIG9iIGplZGVzIGFjY291bnRfaWQgMTIgTW9uYXRlIGhhdA0KbW9udGhfY291bnRzIDwtIG1vbnRoX2NvdW50cyAlPiUgZmlsdGVyKG1vbnRoX2NvdW50ICE9IDEyKQ0KbW9udGhfY291bnRzDQoNCmBgYA0KQXVjaCBoaWVyIGhhYmVuIHdpZWRlciBlaW5pZ2UgS3VuZGVuIHdlbmlnZXIgYWxzIDEyIGtvbnRpbnVpZXJsaWNoZSBNb25hdGUuDQoNCmBgYHtyfQ0Kc3VtbWFyeV9kZiA8LSBzdWJzZXQoc3VtbWFyeV9kZiwgIWFjY291bnRfaWQgJWluJSBtb250aF9jb3VudHMkYWNjb3VudF9pZCkNCmBgYA0KDQoNCmBgYHtyfQ0Kc3VtbWFyeV9kZiA8LSBzdW1tYXJ5X2RmW29yZGVyKHN1bW1hcnlfZGYkYWNjb3VudF9pZCwgcmV2KHN1bW1hcnlfZGYkbW9udGgpKSxdDQpzdW1tYXJ5X2RmJGdyb3VwX2lkIDwtIGF2ZShzZXFfYWxvbmcoc3VtbWFyeV9kZiRhY2NvdW50X2lkKSwgc3VtbWFyeV9kZiRhY2NvdW50X2lkLCBGVU4gPSBmdW5jdGlvbih4KSB7eH0pDQoNCnN1bW1hcnlfZGYkbW9udGhfbnVtYmVyIDwtIDEyDQoNCmZvciAoaSBpbiAyOm5yb3coc3VtbWFyeV9kZikpIHsNCiAgaWYgKHN1bW1hcnlfZGYkYWNjb3VudF9pZFtpXSAhPSBzdW1tYXJ5X2RmJGFjY291bnRfaWRbaS0xXSkgew0KICAgIHN1bW1hcnlfZGYkbW9udGhfbnVtYmVyW2ldIDwtIDEyDQogIH0gZWxzZSB7DQogICAgc3VtbWFyeV9kZiRtb250aF9udW1iZXJbaV0gPC0gc3VtbWFyeV9kZiRtb250aF9udW1iZXJbaS0xXSAtIDENCiAgfQ0KfQ0KDQojIEVudGZlcm5lIGRpZSBTcGFsdGUgZ3JvdXBfaWQNCnN1bW1hcnlfZGYkZ3JvdXBfaWQgPC0gTlVMTA0Kc3VtbWFyeV9kZiRtb250aCA8LSBOVUxMDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5X2RmX25vbl9idXllcnMgPC0gc3VtbWFyeV9kZiAlPiUNCiAgZ3JvdXBfYnkoYWNjb3VudF9pZCkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBtb250aF9udW1iZXIsDQogICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gYyhtYXhfZGlmZmVyZW5jZSwgbWluX2RpZmZlcmVuY2UsIG1heF9iYWxhbmNlLCBtaW5fYmFsYW5jZSwgaW5pdGlhbF9iYWxhbmNlLCBlbmRfYmFsYW5jZSwgbWVhbl9iYWxhbmNlLCBtZWRpYW5fYmFsYW5jZSwgc3RkX2JhbGFuY2UsIG1lZGlhbl9iYWxhbmNlLCBzdGRfYmFsYW5jZSwgbWVhbl9kaWZmZXJlbmNlLCBtZWRpYW5fZGlmZmVyZW5jZSwgc3RkX2RpZmZlcmVuY2UsIGNvdW50X3Bvc2l0aXZlX2RpZmZlcmVuY2UsIGNvdW50X25lZ2F0aXZlX2RpZmZlcmVuY2UpKQ0KYGBgDQoNCkRpZSBUcmFuc2FrdGlvbnNkYXRlbiB3ZXJkZW4gbWl0IGRlbiBhbmRlcmVuIERhdGVuIHp1c2FtbWVuZ2Vmw7xndCwgdW0gcHJvIEt1bmRlIGVpbmUgWmVpbGUgaW4gZWluZW0gRGF0YWZyYW1lIHp1IGhhYmVuLg0KDQpgYGB7cn0NCg0Kc3VtbWFyeV9kZl9ub25fYnV5ZXJzIDwtIG1lcmdlKHN1bW1hcnlfZGZfbm9uX2J1eWVycywgc2ltaWxhcl9ub25fYnV5ZXJzLCBieSA9ICJhY2NvdW50X2lkIikNCmBgYA0KDQpgYGB7cn0NCm1lcmdlKHN1bW1hcnlfZGZfbm9uX2J1eWVycywgbm9uX2J1eWVycywgYnkgPSAiYWNjb3VudF9pZCIpDQpgYGANCg0KDQpgYGB7cn0NCmZpbmFsX2RmIDwtIHJiaW5kKHN1bW1hcnlfZGZfYnV5ZXJzLCBzdW1tYXJ5X2RmX25vbl9idXllcnMpDQpgYGANCg0KSmV0enQgbXVzcyBub2NoIGRhc3MgaXNzdWVkLURhdHVtIHNvd2llIHdlaXRlcmUgVmFyaWFiZWxuIGVudGZlcm50IHdlcmRlbi4NCg0KYGBge3J9DQojIEVudGZlcm5lIHdlaXRlcmUgdW5uw7Z0aWdlIFZhcmlhYmVsbiB3aWUgSUQncyBvZGVyIFdlcnRlLCB3ZWxjaGUgw7xiZXJhbGwgZ2xlaWNoIHNpbmQNCmZpbmFsX2RmIDwtIGZpbmFsX2RmICU+JSBzZWxlY3QoLWNsaWVudF9pZCwgLWRpc3RyaWN0X2lkLCAtZGlzdHJpY3RfaWQuYWNjb3VudHMsIC1kaXNwX2lkLCAtdHlwZSwgLWxvYW5faWQpDQoNCmBgYA0KDQojIyBUcmFpbi1UZXN0LVNwbGl0DQoNCkFscyBWb3JiZXJlaXR1bmcgZsO8ciBkaWUgTW9kZWxsZSBtw7xzc2VuIHdpciB1bnNlcmUgRGF0ZW4genUgVHJhaW5pbmdzLSB1bmQgVGVzdGRhdGVuIHVudGVydGVpbGVuLiBXaXIgbmVobWVuIDgwJSBhbHMgVHJhaW5pbmdzZGF0ZW4NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpzcGxpdCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGZpbmFsX2RmJGhhc19jYXJkLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQp0cmFpbiA8LSBmaW5hbF9kZltzcGxpdCwgXQ0KdGVzdCA8LSBmaW5hbF9kZlstc3BsaXQsIF0NCmBgYA0KDQoNCg0K